/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-10
 * V4.0
 */
/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 */

package com.jphenix.clazz.annotation;

import com.jphenix.standard.docs.ClassInfo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Internal-use only.  This is a helper class internally used for implementing
 * <code>toAnnotationType()</code> in <code>Annotation</code>.
 *   
 * @author Shigeru Chiba
 * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
 * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
 */
@ClassInfo({"2017-02-04 22:20","注释对象实体类"})
@SuppressWarnings({ "rawtypes", "unchecked" })
public class AnnotationImpl implements InvocationHandler {
    private static final String JDK_ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation";
    private static Method JDK_ANNOTATION_TYPE_METHOD = null;
   
    private Annotation annotation;
    private ClassLoader classLoader;
	private transient Class annotationType;
	
    static {
        // Try to resolve the JDK annotation type method
        try {
            Class clazz = Class.forName(JDK_ANNOTATION_CLASS_NAME);
        	
            JDK_ANNOTATION_TYPE_METHOD = clazz.getMethod("annotationType", (Class[])null);
        }
        catch (Exception ignored) {
            // Probably not JDK5+
        }
    }
    
    /**
     * Constructs an annotation object.
     *
     * @param cl        class loader for obtaining annotation types.
     * @param clazz     the annotation type.
     * @param cp        class pool for containing an annotation
     *                  type (or null).
     * @param anon      the annotation.
     * @return the annotation
     */
    public static Object make(ClassLoader cl, Class clazz,
                              Annotation anon) {
        AnnotationImpl handler = new AnnotationImpl(anon, cl);
        return Proxy.newProxyInstance(cl, new Class[] { clazz }, handler);
    }
    
    private AnnotationImpl(Annotation a, ClassLoader loader) {
        annotation = a;
        classLoader = loader;
    }

    /**
     * Obtains the name of the annotation type.
     * 
     * @return the type name
     */
    public String getTypeName() {
        return annotation.getTypeName();
    }

    /**
     * Get the annotation type
     * 
     * @return the annotation class
     * @throws NoClassDefFoundError when the class could not loaded
     */
    private Class getAnnotationType() {
        if (annotationType == null) {
            String typeName = annotation.getTypeName();
            try {
                annotationType = classLoader.loadClass(typeName);
            }
            catch (ClassNotFoundException e) {
                NoClassDefFoundError error = new NoClassDefFoundError("Error loading annotation class: " + typeName);
                error.setStackTrace(e.getStackTrace());
                throw error;
            }
        }
        return annotationType;
    }
    
    /**
     * Obtains the internal data structure representing the annotation.
     * 
     * @return the annotation
     */
    public Annotation getAnnotation() {
        return annotation;
    }

    /**
     * Executes a method invocation on a proxy instance.
     * The implementations of <code>toString()</code>, <code>equals()</code>,
     * and <code>hashCode()</code> are directly supplied by the
     * <code>AnnotationImpl</code>.  The <code>annotationType()</code> method
     * is also available on the proxy instance.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if (Object.class == method.getDeclaringClass()) {
            if ("equals".equals(name)) {
                Object obj = args[0];
                return new Boolean(checkEquals(obj));
            }
            else if ("toString".equals(name)) {
                return annotation.toString();
            } else if ("hashCode".equals(name)) {
                return new Integer(hashCode());
            }
        }
        else if ("annotationType".equals(name)
                 && method.getParameterTypes().length == 0) {
            return getAnnotationType();
        }

        MemberValue mv = annotation.getMemberValue(name);
        if (mv == null) {
            return null;
        } else {
            return mv.getValue(classLoader, method);
        }
    }

    
    /**
     * Check that another annotation equals ourselves.
     * 
     * @param obj the other annotation
     * @return the true when equals false otherwise
     * @throws Exception for any problem
     */
    private boolean checkEquals(Object obj) throws Exception {
        if (obj == null) {
            return false;
        }

        // Optimization when the other is one of ourselves
        if (obj instanceof Proxy) {
            InvocationHandler ih = Proxy.getInvocationHandler(obj);
            if (ih instanceof AnnotationImpl) {
                AnnotationImpl other = (AnnotationImpl) ih;
                return annotation.equals(other.annotation);
            }
        }

        Class otherAnnotationType = (Class) JDK_ANNOTATION_TYPE_METHOD.invoke(obj, (Object[])null);
        if (getAnnotationType().equals(otherAnnotationType) == false) {
            return false;
        }
        
        Method[] methods = annotationType.getDeclaredMethods();
        for (int i = 0; i < methods.length; ++ i) {
            String name = methods[i].getName();

            // Get the value
            MemberValue mv = annotation.getMemberValue(name);
            Object value = null;
            Object otherValue = null;
            try {
               if (mv != null) {
                   value = mv.getValue(classLoader, methods[i]);
               }
               otherValue = methods[i].invoke(obj, (Object[])null);
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Exception e) {
                throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e);
            }

            if (value == null && otherValue != null) {
                return false;
            }
            if (value != null && value.equals(otherValue) == false) {
                return false;
            }
        }
        
        return true;
    }
}
